%==========================================================================
%=  This file is part of the Sound Restoration Project
%=  (c) Copyright Industrial Mathematics Institute
%=                University of South Carolina, Department of Mathematics
%=  ALL RIGHTS RESERVED
%=
%=  Author: Borislav Karaivanov
%==========================================================================

%==========================================================================
% List of the files on which this procedure depends:
%
% calibrateSignalsSingleMask.m
% checkSignalOverlapsAgainstImages.m
% combineSignals.m
% computeOverlapGuess.m
% computeSignalOverlaps.m
% createMissingDirs.m
% findBestFitToSignal.m
% findMinRelativeDifference.m
% fitReduceFitSignal.m
% readdpx.m
% readImageByIndex.m
% readImageFile.m
% setLogWritingFunctHandle.m
% visualizeOverlapGuess.m
% visualizeOverlaps.m
% visualizeSoundCalibrationSingleMask.m
% write2DArrayToFile.m
% writeToLogFile.m
%
% curve fitting toolbox
% image processing toolbox
%
%==========================================================================

%==========================================================================
% The function "extractSoundWithSoundOverlaps" takes a collection of sound
% signals of the same length, calibrates them with a raw (or smoothed)
% additive mask based on their averages, and combines them into a single
% sound signal accounting for their overlaps. The overlaps are computed by
% finding the best match between the adjacent calibrated sound signals.
% REMARK: If infinity is specified as a value of the parameter
% "signalsPerMask" (see below), then all signals are used to compute a
% single calibration mask that is then applied to all of them.
% INPUT: "allSignalsCellArr" is a cell column vector with cells that are 2D
% arrays whose columns hold the individual sound signals.
% "signalsPerMask" is an optional, positive, odd integer specifying the
% number of consecutive signals to be used in the construction of each
% sound-based calibration mask. 
% "sweeps" is an optional non-negative integer specifying the number
% of smoothing passes to be applied to the raw averages of the signals.
% "span" is an optional, positive, odd integer specifying the span
% of the stencil to be used for moving average smoothing of the average
% signal.
% "degOfPolyFitArr" is an optional vector of non-negative integer
% specifying degrees of polynomial to be fitted to the average sound
% signal. The best fitting of these polynomials is to be sampled and used
% as a sound-based calibration mask. If this parameter is specified, then
% the values of the previous two parameters are ignored.
% "rowsToIgnore" is an optional, non-negative integer specifying the
% maximal allowed cumulative number of samples that can be removed at the
% beginning or at the end of the given signal in order to produce a reduced
% signal with a better polynomial fit.
% "framesForOverlapGuess" is an optional, positive integer specifying the
% number of pairs of consecutive signals whose overlaps are to be computed.
% These pairs are to be chosen randomly among all available pairs of
% consecutive signals.
% "initialSearchRadius" is a positive integer specifying the radius of an
% interval centered at the overlap guess where the initial search for the
% best-matching overlap is carried out. It is to reflect the belief of the
% calling function with respect to how far the best-matching overlaps are
% likely to be from the overlap guess.
% "localSearchRadius" is an optional, positive integer specifying the
% radius of the neighborhood (an interval, in fact, in 1D) around a point
% to be checked in order to claim a local minimum at that point. More
% precisely, we consider having a local minimum of the relative differences
% at a fixed overlap value if for all overlaps in the neighborhood of that
% value the relative differences are not smaller.
% "smoothingSpan" is an optional, positive integer specifying the span
% of a moving averaging filter to be applied to the combined signal just
% before it is returned. Value of 1 results in no smoothing at all, i.e.,
% as if no moving averaging filter is applied at all.
% "calibSignalsFileName" is an optional string specifying a name for the
% output text file where all individual, now calibrated, sound signals are
% to be written.
% "waitbarStruct" is an optional parameter specifying a structure of
% parameters used to set a waitbar. Those parameters are a waitbar handle,
% a starting position on the waitbar (a number between 0 and 1), a relative
% length along the waitbar (a number between 0 and (1 - starting position),
% an initial value for a counter that counts the events whose progress is
% being illustrated by the waitbar, and a total reflecting the total number
% of events of the said kind.
% "writeToLogFunctHandle" is an optional function handle which writes to a
% fixed log text file if keeping a log is requested, or does nothing
% otherwise.
% OUTPUT: "signalArr" is column vector returning the single sound signal
% combining all given sound signals.
% "calibSignalsArr" returns a 2D array whose columns hold the individual,
% now calibrated, sound signals.
% "calibSignalsFileName" returns the name of a text file where all
% individual, now calibrated, sound signals are written.
%==========================================================================
function [signalArr, calibSignalsArr, calibSignalsFileName] = ...
    extractSoundWithSoundOverlaps(allSignalsCellArr, signalsPerMask, ...
    sweeps, span, degOfPolyFitArr, rowsToIgnore, framesForOverlapGuess, ...
    initialSearchRadius, localSearchRadius, smoothingSpan, ...
    calibSignalsFileName, waitbarStruct, writeToLogFunctHandle)

if ( (nargin < 2)||(isnumeric(signalsPerMask) == false)|| ...
        (isfinite(signalsPerMask) == false) )
    signalsPerMask = sum(cellfun(@(x) size(x, 2), allSignalsCellArr(:, 1)));
end
if (nargin < 3)
    sweeps = 1;
end
if (nargin < 4)
    span = 15;
end
if (nargin < 5)
    degOfPolyFitArr = 2;
end
if (nargin < 6)
    rowsToIgnore = 100;
end
if (nargin < 7)
    framesForOverlapGuess = 400;
end
if (nargin < 8)
    initialSearchRadius = 10;
end
if (nargin < 9)
    localSearchRadius = 5;
end
if (nargin < 10)
    smoothingSpan = 1;
end
if ( (nargin < 11)||(ischar(calibSignalsFileName) == false) )
    calibSignalsFileName = 'calibSignals.txt';
end
if (nargin < 13)
    writeToLogFunctHandle = @(x) '';
end
% Initialize the output. Otherwise, in case of an early "return" (due to an
% interactive request to cancel) we will get a complaint that an output
% argument has not been assigned during the call to this function.
signalArr = [];
% Ensure that the number of signals contributing to the calibration mask do
% not exceed the number of all available signals.
signalsPerMask = min(signalsPerMask, sum(cellfun(@(x) size(x, 2), allSignalsCellArr(:, 1))));


%=========================================================================%
%========== Here is the essential portion of this function. ==============%
%=========================================================================%
% Calibrate the signals.
[calibSignalsArr, ~, leftSamplesToIgnore, rightSamplesToIgnore] = ...
    calibrateSignalsSingleMask(allSignalsCellArr, sweeps, span, ...
    degOfPolyFitArr, rowsToIgnore);

% Write the calibrated sound signals to a text file.
write2DArrayToFile(calibSignalsFileName, calibSignalsArr, writeToLogFunctHandle);

% Determine the most popular amount of overlap between any two consecutive
% signals.
overlapGuess = computeOverlapGuess(calibSignalsArr, ...
    framesForOverlapGuess, localSearchRadius, writeToLogFunctHandle);

% Determine the amount of overlap between every two consecutive signals.
[overlapArr, minDiffArr] = computeSignalOverlaps(calibSignalsArr, overlapGuess, ...
    initialSearchRadius, localSearchRadius, waitbarStruct(1), writeToLogFunctHandle);
if getappdata(waitbarStruct(1).handle, 'canceling')
	return;
end

% Combine the calibrated sound signals into a single sound signal.
signalArr = combineSignals(calibSignalsArr, overlapArr, waitbarStruct(2));
if getappdata(waitbarStruct(2).handle, 'canceling')
	return;
end

% Smooth the combined sound signal with a moving average filter.
signalArr = smooth(signalArr, smoothingSpan, 'moving');
%=========================================================================%


% if (isempty(overlapArr) == false)
%     
%     % Choose 5 batches from which a signals are to be drawn for visualization.
%     batchIndArr = randi(numel(allSignalsCellArr), min(5, numel(allSignalsCellArr)), 1);
%     % Choose a signal from each of the selected batches.
%     signalToShowIndArr = zeros(numel(batchIndArr), 1);
%     signalsToShowArr = zeros(size(allSignalsCellArr{1}, 1), 2*numel(batchIndArr));
%     for k = 1:numel(batchIndArr)
%         signalToShowIndArr(k) = randi(size(allSignalsCellArr{batchIndArr(k)}, 2) - 1);
%         signalsToShowArr(:, 2*k - 1) = allSignalsCellArr{batchIndArr(k)}(:, signalToShowIndArr(k));
%         signalsToShowArr(:, 2*k) = allSignalsCellArr{batchIndArr(k)}(:, signalToShowIndArr(k) + 1);
%     end
%     
%     % Visualize and save snapshots, if desired.
%     visualizeOverlaps(signalsToShowArr, calibSignalsArr, leftSamplesToIgnore, ...
%         rightSamplesToIgnore, {'Snd'}, {overlapArr}, {minDiffArr}, ...
%         signalsPerMask, sweeps, span, signalToShowIndArr, writeToLogFunctHandle);
% end

% Write info to a log file.
writeToLogFunctHandle(['maximal allowed reduction of signal: ' num2str(rowsToIgnore)]);
writeToLogFunctHandle(['actual reduction of signal at the left (top) end: ' num2str(leftSamplesToIgnore)]);
writeToLogFunctHandle(['actual reduction of signal at the right (bottom) end: ' num2str(rightSamplesToIgnore)]);
writeToLogFunctHandle(['number of smoothing passes: ' num2str(sweeps)]);
writeToLogFunctHandle(['moving average span for mask smoothing: ' num2str(span)]);
writeToLogFunctHandle(['number of signals per mask: ' num2str(signalsPerMask)]);
writeToLogFunctHandle(['moving average span for sound signal smoothing: ' num2str(smoothingSpan)]);
writeToLogFunctHandle(['overlap guess: ' num2str(overlapGuess)]);
writeToLogFunctHandle(['number of random signals used in computing overlap guess: ' num2str(framesForOverlapGuess)]);
writeToLogFunctHandle(['neighborhood radius for local minimum testing: ' num2str(localSearchRadius)]);
writeToLogFunctHandle(['initial search radius for local minimum being global: ' num2str(initialSearchRadius)]);

return;
% end of the function "extractSoundWithSoundOverlaps"
